package com.wuyan.web.base.helper.mongo;

import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.wuyan.web.base.helper.req.CustomGroupParams;
import com.wuyan.web.base.helper.req.CustomQueryOrderParams;
import com.wuyan.web.base.helper.req.CustomQueryParams;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.query.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.regex.Pattern;

/**
 * Mongodb工具类
 */

@Component
public class MongodbHelper {

    public static final String DEF_ID_NAME = "id";
    public static final String DEF_MONGO_ID_NAME = "_id";

    /**
     * 支持的运算符
     */
    private static final Set<String> ops = new HashSet<>();

    static {
        ops.add("eq");
        ops.add("ne");
        ops.add("in");
        ops.add("notIn");
        ops.add("like");
        ops.add("leftLike");
        ops.add("rightLike");
    }

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 保存数据对象，集合为数据对象中@Document 注解所配置的collection
     *
     * @param obj 数据对象
     */
    public <T> T save(T obj) {
        T save = mongoTemplate.save(obj);

        if (save instanceof Map) {
            objectIdToStrHex((Map) obj);
        }

        return save;
    }

    /**
     * 指定集合保存数据对象
     *
     * @param obj            数据对象
     * @param collectionName 集合名
     */
    public <T> T save(T obj, String collectionName) {
        T save = mongoTemplate.save(obj, collectionName);

        if (save instanceof Map) {
            objectIdToStrHex((Map) obj);
        }

        return save;
    }

    /**
     * 根据数据对象中的id删除数据，集合为数据对象中@Document 注解所配置的collection
     *
     * @param obj 数据对象
     */
    public <T> void remove(T obj) {
        mongoTemplate.remove(obj);
    }

    /**
     * 指定集合 根据数据对象中的id删除数据
     *
     * @param obj            数据对象
     * @param collectionName 集合名
     */
    public <T> void remove(T obj, String collectionName) {
        mongoTemplate.remove(obj, collectionName);
    }

    /**
     * 根据key，value到指定集合删除数据
     *
     * @param params         参数
     * @param collectionName 集合名
     */
    public long remove(List<CustomQueryParams> params, String collectionName) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);

        DeleteResult remove = mongoTemplate.remove(query, collectionName);
        return remove.getDeletedCount();
    }

    /**
     * 指定集合 修改数据，且仅修改找到的第一条数据
     *
     * @param params         修改条件
     * @param obj            新数据
     * @param collectionName 集合名
     * @return long
     */
    public long updateFirst(List<CustomQueryParams> params, Map<String, Object> obj, String collectionName) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);


        Update update = createUpdate(obj);
        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, collectionName);
        return updateResult.getModifiedCount();
    }

    /**
     * 指定集合 修改数据，且仅修改找到的第一条数据
     *
     * @param params         修改条件
     * @param obj            新数据
     * @param collectionName 集合名
     * @return long
     */
    public long updateFirst(Map<String, Object> params, Map<String, Object> obj, String collectionName) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);

        Update update = createUpdate(obj);
        UpdateResult updateResult = mongoTemplate.updateFirst(query, update, collectionName);


        return updateResult.getModifiedCount();
    }


    /**
     * 指定集合 修改数据，且修改所找到的所有数据
     *
     * @param params         修改条件
     * @param obj            新数据
     * @param collectionName 集合名
     */
    public void updateMulti(List<CustomQueryParams> params, Map<String, Object> obj, String collectionName) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);

        Update update = createUpdate(obj);

        mongoTemplate.updateMulti(query, update, collectionName);
    }

    /**
     * 根据条件查询出所有结果集 集合为数据对象中@Document 注解所配置的collection
     *
     * @param tClass 数据类类型
     * @param params 查询条件
     * @return List<T>
     */
    public <T> List<T> find(List<CustomQueryParams> params, Class<T> tClass) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);


        List<T> list = mongoTemplate.find(query, tClass);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((List<Map>) list);
        }

        return list;
    }

    /**
     * 指定集合 根据条件查询出所有结果集
     *
     * @param tClass         数据类类型
     * @param params         查询条件
     * @param collectionName 集合名
     * @return List<T>
     */
    public <T> List<T> find(List<CustomQueryParams> params, Class<T> tClass, String collectionName) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);


        List<T> list = mongoTemplate.find(query, tClass, collectionName);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((List<Map>) list);
        }

        return list;
    }


    /**
     * 查询出所有结果集 集合为数据对象中 @Document 注解所配置的collection
     *
     * @param tClass 数据对象
     * @param <T>    T
     * @return List<T>
     */
    public <T> List<T> findAll(Class<T> tClass) {
        List<T> list = mongoTemplate.findAll(tClass);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((List<Map>) list);
        }

        return list;
    }

    /**
     * 指定集合 查询出所有结果集
     *
     * @param tClass         数据对象
     * @param collectionName 文档名
     * @param <T>            T
     * @return List<T>
     */
    public <T> List<T> findAll(Class<T> tClass, String collectionName) {
        List<T> list = mongoTemplate.findAll(tClass, collectionName);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((List<Map>) list);
        }

        return list;
    }


    /**
     * 指定集合 根据条件查询出所有结果集 并排倒序
     *
     * @param tClass         数据类类型
     * @param params         查询条件
     * @param collectionName 集合名
     * @param orderParams    排序字段
     * @return List<T>
     */
    public <T> List<T> find(List<CustomQueryParams> params,
                            List<CustomQueryOrderParams> orderParams,
                            Class<T> tClass,
                            String collectionName) {
        Query query = createFullTextQuery(params);

        Criteria criteria = createCriteria(params);
        if (null != criteria) {
            query.addCriteria(criteria);
        }

        query.with(createSort(orderParams));

        List<T> list = mongoTemplate.find(query, tClass, collectionName);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((List<Map>) list);
        }

        return list;
    }

    /**
     * 指定集合 根据条件分页查询出所有结果集 并排倒序
     *
     * @param tClass         数据类类型
     * @param params         查询条件
     * @param collectionName 集合名
     * @param orderParams    排序字段
     * @param page           当前页
     * @param limit          页大小
     * @return List<T>
     */
    public <T> List<T> find(List<CustomQueryParams> params,
                            List<CustomQueryOrderParams> orderParams,
                            Class<T> tClass,
                            String collectionName,
                            Boolean isPage, Integer page, Integer limit) {
        return find(createFullTextQuery(params), params, orderParams, tClass, collectionName, isPage, page, limit);
    }

    /**
     * 指定集合 根据条件分页查询出所有结果集 并排倒序
     *
     * @param fields         需要被返回的字段
     * @param tClass         数据类类型
     * @param params         查询条件
     * @param collectionName 集合名
     * @param orderParams    排序字段
     * @param page           当前页
     * @param limit          页大小
     * @param <T>            T
     * @return List<T>
     */
    public <T> List<T> find(String[] fields,
                            List<CustomQueryParams> params,
                            List<CustomQueryOrderParams> orderParams,
                            Class<T> tClass,
                            String collectionName,
                            Boolean isPage,
                            Integer page,
                            Integer limit) {
        Query query = createFullTextQuery(params);

        Field fieldsQuery = query.fields();
        fieldsQuery.include(fields);

        return find(query, params, orderParams, tClass, collectionName, isPage, page, limit);
    }

    /**
     * @param query          查询体
     * @param tClass         数据类类型
     * @param params         查询条件
     * @param collectionName 集合名
     * @param orderParams    排序字段
     * @param page           当前页
     * @param limit          页大小
     * @param <T>            T
     * @return List<T>
     */
    public <T> List<T> find(Query query,
                            List<CustomQueryParams> params,
                            List<CustomQueryOrderParams> orderParams,
                            Class<T> tClass,
                            String collectionName,
                            Boolean isPage,
                            Integer page,
                            Integer limit) {
        if (null == query) {
            query = createFullTextQuery(params);
        }

        Criteria criteria = createCriteria(params);
        if (null != criteria) {
            query.addCriteria(criteria);
        }

        query.with(createSort(orderParams));

        if (isPage) {
            query.skip(limit * (page - 1)).limit(limit);
        }

        List<T> list = mongoTemplate.find(query, tClass, collectionName);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((List<Map>) list);
        }

        return list;
    }

    /**
     * 分组统计
     *
     * @param collectionName  集合名称
     * @param tClass          返回的实例类类型
     * @param unwind          数组拆分标识
     * @param groupFieldNames 分组字段名
     * @param groupParams     分组参数
     * @param params          查询条件
     * @param <T>             T
     * @return List<T>
     */
    public <T> List<T> countByGroup(String collectionName,
                                    Class<T> tClass,
                                    String unwind,
                                    String[] groupFieldNames,
                                    List<CustomGroupParams> groupParams,
                                    List<CustomQueryParams> params) {
        List<AggregationOperation> operations = new ArrayList<>();

        // 查询条件
        Criteria criteria = createCriteria(params);
        if (null != criteria) {
            operations.add(Aggregation.match(criteria));
        }

        // 数组拆分标识
        if (StringUtils.isNotBlank(unwind)) {
            operations.add(Aggregation.unwind(unwind));
        }

        // 分组统计参数
        GroupOperation groupOperation = createGroup(groupFieldNames, groupParams);
        operations.add(groupOperation);
        Aggregation agg = Aggregation.newAggregation(operations);

        AggregationResults<T> results = mongoTemplate.aggregate(agg, collectionName, tClass);
        return results.getMappedResults();
    }


    /**
     * 统计条目数
     *
     * @param tClass         数据类类型
     * @param params         查询条件
     * @param collectionName 集合名
     * @param orderParams    排序字段
     * @param <T>            t
     * @return long
     */
    public <T> long count(List<CustomQueryParams> params,
                          List<CustomQueryOrderParams> orderParams,
                          Class<T> tClass,
                          String collectionName) {
        Query query = createFullTextQuery(params);

        Criteria criteria = createCriteria(params);
        if (null != criteria) {
            query.addCriteria(criteria);
        }

        query.with(createSort(orderParams));

        return mongoTemplate.count(query, tClass, collectionName);
    }

    /**
     * 根据条件查询出符合的第一条数据 集合为数据对象中 @Document 注解所配置的collection
     *
     * @param tClass 数据类类型
     * @param params 查询条件
     * @return T
     */
    public <T> T findOne(List<CustomQueryParams> params, Class<T> tClass) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);


        T one = mongoTemplate.findOne(query, tClass);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((Map) one);
        }

        return one;
    }

    /**
     * 指定集合 根据条件查询出符合的第一条数据
     *
     * @param tClass         数据对象
     * @param params         查询条件
     * @param collectionName 集合名
     * @return T
     */
    public <T> T findOne(List<CustomQueryParams> params, Class<T> tClass, String collectionName) {
        Criteria criteria = createCriteria(params);
        Query query = Query.query(criteria);


        T one = mongoTemplate.findOne(query, tClass, collectionName);

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((Map) one);
        }

        return one;
    }

    public <T> T findOne(String id, Class<T> tClass, String collectionName) {
        T one = mongoTemplate.findById(id, tClass, collectionName);

        if (null == one) return one;

        if (tClass.isAssignableFrom(Map.class)) {
            objectIdToStrHex((Map) one);
        }

        return one;
    }

    public boolean createFullText(String collectionName) {
        BasicDBObject index = new BasicDBObject();
        index.put("cfg_tags", "text");
        String res = mongoTemplate.getCollection(collectionName).createIndex(index);
        return true;
    }

    /**
     * 创建查询内容
     *
     * @param params 查询参数
     * @return Criteria
     */
    private Criteria createCriteria(Map<String, Object> params) {
        if (null == params || params.isEmpty()) {
            return null;
        }

        Criteria criteria = null;

        int i = 0;
        for (String key : params.keySet()) {
            if (i == 0) {
                criteria = Criteria.where(key).is(params.get(key));
            } else {
                criteria.and(key).is(params.get(key));
            }

            i++;
        }

        return criteria;
    }

    /**
     * 创建查询内容
     *
     * @param params 查询参数
     * @return Criteria
     */
    private Criteria createCriteria(List<CustomQueryParams> params) {
        if (null == params || params.isEmpty()) {
            return null;
        }

        // 分类
        Map<String, List<CustomQueryParams>> map = new HashMap<>();
        for (CustomQueryParams param : params) {
            List<CustomQueryParams> customQueryParams;

            if (map.containsKey(param.getTeam())) {
                customQueryParams = map.get(param.getTeam());
            } else {
                customQueryParams = new ArrayList<>();
            }

            customQueryParams.add(param);

            map.put(StringUtils.isBlank(param.getTeam()) ? "basc" : param.getTeam(), customQueryParams);
        }

        Criteria criteria = new Criteria();

        // 输出
        Set<String> teams = map.keySet();
        for (String team : teams) {
            if ("or".equals(team)) {
                criteria.andOperator(new Criteria().orOperator(getCriteria(map.get(team))));
            } else {
                for (CustomQueryParams t : params) {
                    criteria = createCriteria(criteria, t);
                }
            }
        }

        return criteria;
    }

    /**
     * 查询条件转换
     *
     * @param params 查询条件
     * @return Criteria[]
     */
    private Criteria[] getCriteria(List<CustomQueryParams> params) {
        if (null == params || params.isEmpty()) {
            return new Criteria[0];
        }

        Criteria[] criteria = new Criteria[params.size()];

        int index = 0;
        for (CustomQueryParams param : params) {
            criteria[index] = createCriteria(null, param);
        }

        return criteria;
    }

    /**
     * 单个条件转换
     *
     * @param criteria c
     * @param t        查询条件
     * @return Criteria
     */
    private Criteria createCriteria(Criteria criteria, CustomQueryParams t) {
        if (t.getRight().length < 1 || null == t.getRight()[0] || null == t.getOp() || null == t.getLeft()) {
            return null == criteria ? new Criteria() : criteria;
        }

        if (!ops.contains(t.getOp())) {
            return null == criteria ? new Criteria() : criteria;
        }

        if (DEF_ID_NAME.equals(t.getLeft())) {
            t.setLeft(DEF_MONGO_ID_NAME);
        }

        if (null == criteria) {
            criteria = Criteria.where(t.getLeft());
        } else {
            criteria = criteria.and(t.getLeft());
        }

        switch (t.getOp()) {
            // 等于
            case "eq":
                criteria = criteria.is(t.getRight()[0]);
                break;
            // 不等于
            case "ne":
                criteria = criteria.ne(t.getRight()[0]);
                break;
            // 包含
            case "in":
                criteria = criteria.in(t.getRight());
                break;
            // 不包含
            case "notIn":
                criteria = criteria.nin(t.getRight());
                break;
            // 模糊匹配
            case "like":
                Pattern pattern = Pattern.compile("^.*" + escapeExprSpecialWord(t.getRight()[0].toString()) + ".*$", Pattern.CASE_INSENSITIVE);
                criteria = criteria.regex(pattern);
                break;
            // 左模糊匹配
            case "leftLike":
                Pattern leftPattern = Pattern.compile("^" + escapeExprSpecialWord(t.getRight()[0].toString()) + ".*$", Pattern.CASE_INSENSITIVE);
                criteria = criteria.regex(leftPattern);
                break;
            // 右模糊匹配
            case "rightLike":
                Pattern rightPattern = Pattern.compile("^.*" + escapeExprSpecialWord(t.getRight()[0].toString()) + "$", Pattern.CASE_INSENSITIVE);
                criteria = criteria.regex(rightPattern);
                break;
            default:
                break;
        }

        return criteria;
    }


    /**
     * 创建排序
     *
     * @param orderParams 排序参数
     * @return Sort
     */
    private Sort createSort(List<CustomQueryOrderParams> orderParams) {
        if (null == orderParams || orderParams.isEmpty()) {
            return Sort.by(Sort.Order.desc("create_time"));
        }

        List<Sort.Order> orders = new ArrayList<>();
        int i = 0;
        for (CustomQueryOrderParams orderParam : orderParams) {
            if (!orderParam.check()) {
                continue;
            }

            if ("id".equals(orderParam.getFiledName())) {
                orderParam.setFiledName("_id");
            }

            if ("DESC".equals(orderParam.getOrderName())) {
                orders.add(Sort.Order.desc(orderParam.getFiledName()));
            } else {
                orders.add(Sort.Order.asc(orderParam.getFiledName()));
            }

            i++;
        }

        return Sort.by(orders);
    }

    /**
     * 创建更新内容
     *
     * @param obj 数据
     * @return Update
     */
    private Update createUpdate(Map<String, Object> obj) {
        if (null == obj || obj.isEmpty()) {
            return null;
        }

        Update update = new Update();
        for (String key : obj.keySet()) {
            if ("_id".equals(key) || "create_time".equals(key)) {
                continue;
            }

            update.set(key, obj.get(key));
        }

        return update;
    }

    private GroupOperation createGroup(String[] groupFieldNames,
                                       List<CustomGroupParams> groupParams) {
        final GroupOperation[] groupOperation = {Aggregation.group(groupFieldNames)};
        groupParams.forEach(t -> {
            switch (t.getOp()) {
                case "stdDevPop":
                    groupOperation[0] = groupOperation[0].stdDevPop(t.getFieldName()).as(t.getValueName());
                    break;
                case "stdDevSamp":
                    groupOperation[0] = groupOperation[0].stdDevSamp(t.getFieldName()).as(t.getValueName());
                    break;
                case "max":
                    groupOperation[0] = groupOperation[0].max(t.getFieldName()).as(t.getValueName());
                    break;
                case "min":
                    groupOperation[0] = groupOperation[0].min(t.getFieldName()).as(t.getValueName());
                    break;
                case "avg":
                    groupOperation[0] = groupOperation[0].avg(t.getFieldName()).as(t.getValueName());
                    break;
                case "first":
                    groupOperation[0] = groupOperation[0].first(t.getFieldName()).as(t.getValueName());
                    break;
                case "last":
                    groupOperation[0] = groupOperation[0].last(t.getFieldName()).as(t.getValueName());
                    break;
                case "sum":
                    groupOperation[0] = groupOperation[0].sum(t.getFieldName()).as(t.getValueName());
                    break;
                case "count":
                    groupOperation[0] = groupOperation[0].count().as(t.getValueName());
                default:
                    break;
            }
        });

        return groupOperation[0];
    }

    /**
     * ID转化
     *
     * @param objs 数据集合
     */
    private void objectIdToStrHex(List<Map> objs) {
        for (Map obj : objs) {
            objectIdToStrHex(obj);
        }
    }

    /**
     * ID转化
     *
     * @param obj 数据
     */
    private void objectIdToStrHex(Map obj) {
        if (obj.containsKey("_id")) {
            Object id = obj.get("_id");
            obj.put("id", id.toString());
            obj.remove("_id");
        }
    }

    /**
     * regex对输入特殊字符转义
     *
     * @param keyword 查询关键字
     * @return String
     */
    private String escapeExprSpecialWord(String keyword) {
        if (StringUtils.isNotBlank(keyword)) {

            String[] fbsArr = {"\\", "$", "(", ")", "*", "+", ".", "[", "]", "?", "^", "{", "}", "|"};

            for (String key : fbsArr) {

                if (keyword.contains(key)) {
                    keyword = keyword.replace(key, "\\" + key);
                }
            }
        }

        return keyword;
    }

    /**
     * 判断是否需要建立全文索引
     *
     * @param params 查询参数集合
     * @return Query
     */
    private Query createFullTextQuery(List<CustomQueryParams> params) {
        DBObject obj = new BasicDBObject();

        for (CustomQueryParams param : params) {
            if ("cfg_tags".equals(param.getLeft()) && "text".equals(param.getOp())) {
                DBObject search = new BasicDBObject();
                search.put("$search", param.getRight()[0]);
                obj.put("$text", search);
                break;
            }
        }

        String p = obj.toString();

        return new BasicQuery(p);
    }
}
